/*  Copyright © 2009 Blake Hegerle -*- C++ -*- */

/*  This file is part of metal.
 
    Metal is free software: you can redistribute it and/or modify it
    under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
    
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    General Public License for more details.
 
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see
    <http://www.gnu.org/licenses/>.

*/

#pragma once

#include <type_traits>
#include <deque>

#include "expr_impl.h"
#include "assignment.h"
#include "call.h"
#include "util.h"
#include "meta/expr.h"
#include "meta/trans.h"
#include "meta/proto.h"
#include "expression_conversion.h"
#include "function.h"
#include "decl.h"
#include "def.h"
#include "lambda.h"

namespace metal {
  template<typename I>
  struct member_selector { };

  template<typename MetaExpr>
  class expression {
    typedef meta::get_identifier<MetaExpr> ident;
  public:    
    expression(expr_impl *e) : ip(e) {}
    expression(expr_ptr e) : ip(e) {}

    const expr_ptr &impl() const { return ip; }

    template<typename IdxExpr>
    expression<
      meta::subscript_expr<
	MetaExpr,
	IdxExpr
	>
      > 
    operator[](const expression<IdxExpr> &e) {
      return new subscript_expr_impl(impl(), e.impl());
    }

  template<typename T>
  expression<
    meta::subscript_expr<
      MetaExpr, 
      meta::constant_expr<T>
      >
    > 
  operator[](T e) {
    static_assert(std::is_integral<T>::value, 
		  "subscript with constant requires integral type");
    return new subscript_expr_impl(impl(), cnst_impl(e));
  }
    
    template<typename Member>
    expression<
      meta::dot_expr<
	MetaExpr, Member
	>
      >
    operator[](member_selector<Member> s) {
      return new dot_expr_impl(impl(), name_of<Member>());
    }

  expression<meta::call_expr<MetaExpr> > operator()() {
    std::vector<expr_ptr> c({ impl() });

    return new call_expr_impl(c.begin(), c.end());
  }

  template<typename... Args>
  expression<typename meta::call_trans<MetaExpr, Args...>::type> 
  operator()(Args... args) {
    argument_consumer a;
    a.consume(args...);

    std::deque<expr_ptr> children(a.begin(), a.end());
    children.push_front(impl());

    return new call_expr_impl(children.begin(), children.end());
  }
private:
  expr_ptr ip;
};

template<typename Ident>
class expression<meta::ident_expr<Ident> > {
  typedef meta::ident_expr<Ident> MetaExpr;
public:    
  expression(expr_impl *e) : ip(e) {}
  expression(expr_ptr e) : ip(e) {}

  const expr_ptr &impl() const { return ip; }

  template<typename IdxExpr>
  expression<
    meta::subscript_expr<
      MetaExpr,
      IdxExpr
      >
    > 
  operator[](const expression<IdxExpr> &e) {
    return new subscript_expr_impl(impl(), e.impl());
  }

  template<typename T>
  expression<
    meta::subscript_expr<
      MetaExpr, 
      meta::constant_expr<T>
      >
    > 
  operator[](T e) {
    static_assert(std::is_integral<T>::value, "subscript with constant requires integral type");
    return new subscript_expr_impl(impl(), cnst_impl(e));
  }
  
  member_selector<Ident> operator&() {
    return member_selector<Ident>();
  }

  template<typename Member>
  expression<
    meta::dot_expr<
      MetaExpr, Member
      >
    >
  operator[](member_selector<Member> s) {
    return new dot_expr_impl(impl(), name_of<Member>());
  }

  template<typename ThatExpr>
  assignment<
    true, 
    Ident,
    ThatExpr
    >
  operator=(const expression<ThatExpr> &e) {
    return assignment<true, Ident, ThatExpr>(impl(), e.impl());
  }
#define PARALLEL_CONSTANT_ASSIGN(PT)					\
  assignment<true, Ident, meta::constant_expr<PT> > operator=(PT t) {	\
    return assignment<true, Ident, meta::constant_expr<PT> >(impl(),	\
							     cnst_impl(t)); }

  PARALLEL_CONSTANT_ASSIGN(signed char);
  PARALLEL_CONSTANT_ASSIGN(short);
  PARALLEL_CONSTANT_ASSIGN(int);
  PARALLEL_CONSTANT_ASSIGN(long);
  PARALLEL_CONSTANT_ASSIGN(unsigned char);
  PARALLEL_CONSTANT_ASSIGN(unsigned short);
  PARALLEL_CONSTANT_ASSIGN(unsigned int);
  PARALLEL_CONSTANT_ASSIGN(unsigned long);
  PARALLEL_CONSTANT_ASSIGN(float);
  PARALLEL_CONSTANT_ASSIGN(double);

  template<typename Type, typename... Args>
  declaration<Type, Ident, Args...> operator=(const anonymous_declaration<Type, Args...> &d) {
    return declaration<Type, Ident, Args...>();
  }

  template<typename ThatExpr>
  assignment<false, Ident, ThatExpr> operator*=(const expression<ThatExpr> &e) {
    return assignment<false, Ident, ThatExpr>(impl(), e.impl());
  }

  expression<meta::call_expr<MetaExpr> > operator()() {
    std::vector<expr_ptr> c({ impl() });

    return new call_expr_impl(c.begin(), c.end());
  }

  template<typename... Args>
  expression<typename meta::call_trans<MetaExpr, Args...>::type> 
  operator()(Args... args) {
    argument_consumer a;
    a.consume(args...);

    std::deque<expr_ptr> children(a.begin(), a.end());
    children.push_front(impl());

    return new call_expr_impl(children.begin(), children.end());
  }
private:
  expr_ptr ip;
};

  // operators
  
#define PARALLEL_EXPR_OP(op, t)					\
  template<typename Left, typename Right>			\
  expression<meta::binary_expr					\
	     <meta::t##_tag, Left, Right>			\
	     > operator op					\
  (const expression<Left> &l, const expression<Right> &r)	\
  { return new binary_expr_impl(#op, l.impl(), r.impl()); }
  
#define PARALLEL_EXPR_UOP(op, t)				\
  template<typename Expr>					\
  expression<meta::unary_expr<meta::t##_tag, Expr>> operator op	\
    (const expression<Expr> &e)					\
  { return new unary_expr_impl(#op[0], e.impl()); }
  
  /* addition is handled as a special case */
  PARALLEL_EXPR_OP(+, arithmatic);
  PARALLEL_EXPR_OP(-, arithmatic);
  PARALLEL_EXPR_UOP(-, arithmatic);
  PARALLEL_EXPR_OP(*, arithmatic);
  PARALLEL_EXPR_OP(/, arithmatic);

  PARALLEL_EXPR_OP(==, comparison);
  PARALLEL_EXPR_OP(!=, comparison);
  PARALLEL_EXPR_OP(<, comparison);
  PARALLEL_EXPR_OP(>, comparison);
  PARALLEL_EXPR_OP(<=, comparison);
  PARALLEL_EXPR_OP(>=, comparison);

  PARALLEL_EXPR_OP(%, integral);
  PARALLEL_EXPR_OP(&, integral);
  PARALLEL_EXPR_OP(|, integral);
  PARALLEL_EXPR_OP(<<, integral);
  PARALLEL_EXPR_OP(>>, integral);
  PARALLEL_EXPR_OP(^, integral);
  PARALLEL_EXPR_UOP(~, integral);

  PARALLEL_EXPR_OP(&&, boolean);
  PARALLEL_EXPR_OP(||, boolean);
  PARALLEL_EXPR_UOP(!, boolean);

#define PARALLEL_LEFT_PRIMITIVE_EXPR_OP(op, primitive_type, t)		\
  template<typename MetaExpr>						\
  expression<meta::binary_expr<meta::t##_tag,				\
			       meta::constant_expr<primitive_type>,	\
			       MetaExpr>				\
	     > operator op						\
  (primitive_type c, const expression<MetaExpr> &e)			\
  { return new binary_expr_impl(#op, cnst_impl(c), e.impl()); }
  
#define PARALLEL_RIGHT_PRIMITIVE_EXPR_OP(op, primitive_type, t)		\
  template<typename MetaExpr>						\
  expression<meta::binary_expr<meta::t##_tag,	MetaExpr,		\
			       meta::constant_expr<primitive_type>>	\
	     > operator op						\
    (const expression<MetaExpr> &e, primitive_type c)			\
  { return new binary_expr_impl(#op, e.impl(), cnst_impl(c)); }
    
#define PARALLEL_PRIMITIVE_EXPR_OP(op, primitive_type, t)	\
  PARALLEL_LEFT_PRIMITIVE_EXPR_OP(op, primitive_type, t);	\
  PARALLEL_RIGHT_PRIMITIVE_EXPR_OP(op, primitive_type, t);
    
  PARALLEL_PRIMITIVE_EXPR_OP(&&, bool, boolean);
  PARALLEL_PRIMITIVE_EXPR_OP(||, bool, boolean);
    
#define PRIMITIVE_NUMERIC_OP(type)			\
  PARALLEL_PRIMITIVE_EXPR_OP(+, type, arithmatic);	\
  PARALLEL_PRIMITIVE_EXPR_OP(-, type, arithmatic);	\
  PARALLEL_PRIMITIVE_EXPR_OP(*, type, arithmatic);	\
  PARALLEL_PRIMITIVE_EXPR_OP(/, type, arithmatic);	\
  PARALLEL_PRIMITIVE_EXPR_OP(%, type, integral);	\
  PARALLEL_PRIMITIVE_EXPR_OP(==, type, comparison);	\
  PARALLEL_PRIMITIVE_EXPR_OP(!=, type, comparison);	\
  PARALLEL_PRIMITIVE_EXPR_OP(<, type, comparison);	\
  PARALLEL_PRIMITIVE_EXPR_OP(>, type, comparison);	\
  PARALLEL_PRIMITIVE_EXPR_OP(<=, type, comparison);	\
  PARALLEL_PRIMITIVE_EXPR_OP(>=, type, comparison);
  
#define PRIMITIVE_INTEGRAL_OP(type)			\
  PARALLEL_PRIMITIVE_EXPR_OP(&, type, integral);	\
  PARALLEL_PRIMITIVE_EXPR_OP(|, type, integral);	\
  PARALLEL_PRIMITIVE_EXPR_OP(<<, type, integral);	\
  PARALLEL_PRIMITIVE_EXPR_OP(>>, type, integral);	\
  PARALLEL_PRIMITIVE_EXPR_OP(^, type, integral);	\
  PRIMITIVE_NUMERIC_OP(type);

  PRIMITIVE_INTEGRAL_OP(signed char);
  PRIMITIVE_INTEGRAL_OP(short);
  PRIMITIVE_INTEGRAL_OP(int);
  PRIMITIVE_INTEGRAL_OP(long);
  PRIMITIVE_INTEGRAL_OP(unsigned char);
  PRIMITIVE_INTEGRAL_OP(unsigned short);
  PRIMITIVE_INTEGRAL_OP(unsigned int);
  PRIMITIVE_INTEGRAL_OP(unsigned long);
  PRIMITIVE_NUMERIC_OP(float);
  PRIMITIVE_NUMERIC_OP(double);

  template<typename I>
  expression<meta::call_expr<I> > call(expression<meta::ident_expr<I> > m) {
    return new call_expr_impl(name_of<I>());
  }
  
  template<
    typename T, 
    typename... Args
  > 
  expression<typename meta::call_trans<T, Args...>::type>
  call(expression<T> m, Args... args) 
  {
    argument_consumer a;
    
    a.consume(args...);
    
    return new call_expr_impl(m.impl(), a.begin(), a.end());
  }

  template<typename I, typename T, typename E>
  expression<
    meta::cond_expr<
      typename expression_type_of<I>::meta_type,
      typename expression_type_of<T>::meta_type,
      typename expression_type_of<E>::meta_type
      >
    >
  cond(I i, T t, E e) {
    return new cond_expr_impl
      (expression_type_of<I>::impl(i),
       expression_type_of<T>::impl(t),
       expression_type_of<E>::impl(e));
  }
  
  template<typename I, typename Range, typename F>
  expression<
    meta::filter_expr<
      meta::ident_expr<I>,
      I,
      Range,
      typename expression_type_of<F>::meta_type
      >
    > 
  filter(const assignment<false, I, Range> &a, F f)
  {
    return new filter_expr_impl(new_ptr<ident_expr_impl>(a.impl()->target()),
				a.impl(), 
				expression_type_of<F>::impl(f));
  }
  
  template<typename E, typename I, typename Range, typename F>
  expression<
    meta::filter_expr<
      typename expression_type_of<E>::meta_type,
      I, 
      Range, 
      typename expression_type_of<F>::meta_type
      > 
    >
  filter(E e, const assignment<false, I, Range> &a, F f) {
    return new filter_expr_impl(expression_type_of<E>::impl(e), 
				a.impl(), 
				expression_type_of<F>::impl(f));
  }
  
  template<typename Expr, typename... Assigns>
  expression<
    typename meta::eval_meta_trans<meta::eval_expr<Expr>, Assigns...>::type
    > 
  eval(const expression<Expr> &e, Assigns... assigns) {
    static_assert(count<Assigns...>::value > 0, 
		  "eval must have at least one scalar assignment following the expression");

    assignment_consumer<true> ac;
    ac.consume(assigns...);
    
    return new eval_expr_impl(e.impl(), ac.begin(), ac.end());
  }

  template<typename Expr, typename... Assigns>
  expression<
    typename meta::map_meta_trans<meta::map_expr<Expr>, Assigns...>::type
    > 
  map(const expression<Expr> &e, Assigns... assigns) {
    static_assert(count<Assigns...>::value > 0, 
		  "eval must have at least one range assignment following the expression");
    
    assignment_consumer<false> ac;
    ac.consume(assigns...);
    
    return new map_expr_impl(e.impl(), ac.begin(), ac.end());
  }
  
  template<typename... Args>
  expression<
    typename meta::array_trans<Args...>::type
    >
  array(Args... args) {
    argument_consumer ac;
    ac.consume(args...);
    
    return new array_expr_impl(ac.begin(), ac.end());
  }

  // the arguments to this function can be in any order
  template<typename T, typename... Args>
  expression<
    typename meta::cons_trans<T, Args...>::type
    > 
  cons(Args... assignments) {
    assignment_consumer<true> ac;
    ac.consume(assignments...);
    
    return new cons_expr_impl(lower<T>(), ac.begin(), ac.end());
  }
  
  template<typename T, typename MetaExpr>
  expression<
    meta::cast_expr<T, MetaExpr>
    >
  cast(const expression<MetaExpr> &e) {
    return new cast_expr_impl(lower<T>(), e.impl());
  }

  template<typename T, typename... Params>
  struct lambda_placeholder;

  template<typename T, typename... Params>
  lambda_placeholder<T, Params...>
  lambda(Params... params) {
    return lambda_placeholder<T, Params...>();
  }
}
